import {
  Object3D,
  BoxGeometry,
  MeshLambertMaterial,
  BufferGeometry,
  InstancedMesh,
  Matrix4,
  Vector2,
  Color,
} from 'three'
import { BufferGeometryUtils } from 'three/addons'

export { mergeGeometries, buildInstancedMesh, linesToWalls }

function collectGeometries(target: Object3D, cache: BufferGeometry[] = []) {
  if ('geometry' in target) {
    cache.push(target.geometry as BufferGeometry)
  }
  target.children.forEach((c) => collectGeometries(c, cache))
  return cache
}

function mergeGeometries(target: Object3D) {
  const geoms = collectGeometries(target)
  return BufferGeometryUtils.mergeGeometries(geoms)
}

type BuildInstancedParams = {
  rotate?: number
  scale?: [x: number, y: number, z: number]
  position?: { x: number; y: number; z?: number }
}

function buildInstancedMesh(
  geom: BufferGeometry,
  params: BuildInstancedParams[],
  material?: MeshLambertMaterial
) {
  const temp = new Matrix4()
  const im = new InstancedMesh(geom, material || new MeshLambertMaterial(), params.length)
  params.forEach((p, n) => {
    const m = new Matrix4()
    if (p.scale) m.premultiply(temp.makeScale(...p.scale))
    if (p.rotate) m.premultiply(temp.makeRotationY(p.rotate))
    if (p.position) {
      const z = p.position.z || 0
      m.premultiply(temp.makeTranslation(p.position.x, z, p.position.y))
    }
    im.setMatrixAt(n, m)
  })
  return im
}

/** 从线段坐标和参数的数组生成墙体的 instancedMesh。Y轴反向了 */
function linesToWalls(
  input: {
    lines: { x: number; y: number }[]
    width: number
    height: number
    color: string
  }[]
) {
  const geom = new BoxGeometry(1, 1, 1)
  const mat = new MeshLambertMaterial()
  const count = input.reduce((v, p) => (v += p.lines.length - 1), 0)
  const im = new InstancedMesh(geom, mat, count)

  let index = 0
  const temp = new Matrix4()
  input.forEach((params) => {
    const { lines, color, height, width } = params
    for (let i = 0; i < lines.length - 1; i++) {
      const p1 = lines[i]!
      const p2 = lines[i + 1]!
      const centerX = (p1.x + p2.x) / 2
      const centerY = (p1.y + p2.y) / 2
      const v = new Vector2(p2.x - p1.x, p2.y - p1.y)
      const m = new Matrix4()
        .premultiply(temp.makeTranslation(0, 0.5, 0))
        .premultiply(temp.makeScale(v.length(), height, width))
        .premultiply(temp.makeRotationY(v.angle()))
        .premultiply(temp.makeTranslation(centerX, 0, -centerY))
      im.setMatrixAt(index, m)
      im.setColorAt(index, new Color(color))
      index++
    }
  })

  return im
}
